#pragma once

#include "DisallowConversion.h"
#include "DefaultSPStorage.h"
#include "RefCounted.h"
#include "AssertCheck.h"

#include "TemplateUtil.h"

template
<
	typename T,
	template <class> class StoragePolicy = DefaultSPStorage,
	template <class> class OwnershipPolicy = RefCounted,
	class ConversionPolicy = DisallowConversion,
	template <class> class CheckingPolicy = AssertCheck
>
class SmartPtr :
	public StoragePolicy<T>,
	public OwnershipPolicy<typename StoragePolicy<T>::PointerType>,
	public ConversionPolicy,
	public CheckingPolicy<typename StoragePolicy<T>::StoredType>
{

typedef StoragePolicy<T> SP;
typedef OwnershipPolicy<typename SP::PointerType> OP;
typedef ConversionPolicy CP;
typedef CheckingPolicy<typename SP::StoredType> CK;

public:

typedef typename SP::StoredType StoredType;
typedef typename SP::ReferenceType ReferenceType;
typedef typename SP::PointerType PointerType;

typedef typename IF<OP::destructiveCopy, SmartPtr, const SmartPtr>::RET CopyArg;

	SmartPtr(void) : 
		SP(),
		OP(),
		CP(),
		CK()
	{
		CK::OnDefault(GetImpl(*this));
	}

	SmartPtr(StoredType iPointee) : 
		SP(iPointee),
		OP(),
		CP(),
		CK()
	{
		CK::OnInit(GetImpl(*this));
	}

	SmartPtr(CopyArg& rhs) :
		SP(rhs),
		OP(rhs),
		CP(rhs),
		CK(rhs)
	{
		GetImplRef(*this) = OP::Clone(GetImplRef(rhs));
	}

	template
	<
		typename T1,
		template <class> class SP1,
		template <class> class OP1,
		class CP1,
		template <class> class CK1
	>
	SmartPtr(SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs) :
		SP(rhs),
		OP(rhs),
		CP(rhs),
		CK(rhs)
	{
		GetImplRef(*this) = OP::Clone(GetImplRef(rhs));
	}

	template
	<
		typename T1,
		template <class> class SP1,
		template <class> class OP1,
		class CP1,
		template <class> class CK1
	>
	SmartPtr(const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs) : // TODO: suppress auto-generated copy-ctor
		SP(rhs),
		OP(rhs),
		CK(rhs),
		CP(rhs)
	{
		GetImplRef(*this) = OP::Clone(GetImplRef(rhs));
	}

	~SmartPtr(void)
	{
		if(OP::Release(GetImpl(*static_cast<SP*>(this))))	// reference at this obj as SP
		{
			SP::Destroy();
		}
	}

	// Global functions

	friend inline void Release(SmartPtr& ioSp, StoredType& oDst)
	{
		oDst = GetImplRef(ioSp);
		GetImplRef(ioSp) = SP::Default();
	}

	friend inline void Reset(SmartPtr& iSp, StoredType iSrc)
	{
		// TODO: impl: create and swap
	}


	// Assignment

	SmartPtr& operator= (CopyArg& rhs)
	{
		SmartPtr temp(rhs);
		temp.Swap(*this);
		return *this;
	}

	template
	<
		typename T1,
		template <class> class SP1,
		template <class> class OP1,
		class CP1,
		template <class> class CK1
	>
	SmartPtr& operator= (const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs)
	{
		SmartPtr temp(rhs);
		temp.Swap(*this);
		return *this;
	}

	template
	<
		typename T1,
		template <class> class SP1,
		template <class> class OP1,
		class CP1,
		template <class> class CK1
	>
	SmartPtr& operator= (SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs)
	{
		SmartPtr temp(rhs);
		temp.Swap(*this);
		return *this;
	}

	void Swap(SmartPtr &rhs)
	{
		SP::Swap(rhs);
		OP::Swap(rhs);
		CP::Swap(rhs);
		CK::Swap(rhs);
	}


	// Point-to - Dereference

	ReferenceType operator* ()
	{
		CK::OnDereference(GetImplRef(*this));
		return SP::operator*();
	}

	ReferenceType operator* () const
	{
		CK::OnDereference(GetImplRef(*this));
		reuturn SP::operator*();
	}

	PointerType operator-> ()
	{
		CK::OnDereference(GetImplRef(*this));
		reuturn SP::operator->();
	}

	PointerType operator-> () const
	{
		CK::OnDereference(GetImplRef(*this));
		reuturn SP::operator->();
	}


	// Comparison
	
	bool operator! () const
	{
		return GetImpl(*this) == nullptr;
	}

	// Comparison functions reminder:
	// SP == T*		: Handle 0 and nullptr conversion (templates don't use conversions)
	// SP == <U>*	: Handle T inheritance
	// SP == SP<U>	: Handle other types of SmartPointer

	// Comparison with raw T pointers + 0 conversion (template matching does not include conversions)

	inline friend bool operator== (const SmartPtr& lhs, const T* rhs)
	{
		return GetImpl(lhs) == rhs;
	}

	inline friend bool operator== (const T* lhs, const SmartPtr& rhs)
	{
		return rhs == lhs;
	}

	inline friend bool operator!= (const SmartPtr& lhs, const T* rhs)
	{
		return !(GetImpl(lhs) == rhs);
	}

	inline friend bool operator!= (const T* lhs, const SmartPtr& rhs)
	{
		return rhs != lhs;
	}

	// Resolve ambiguous calls (lhs vs rhs template operator)

	template
	<
		typename T1,
		template <class> class SP1,
		template <class> class OP1,
		class CP1,
		template <class> class CK1
	>
	bool operator== (SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs) const
	{
		return *this == GetImpl(rhs);
	}

	template
	<
		typename T1,
		template <class> class SP1,
		template <class> class OP1,
		class CP1,
		template <class> class CK1
	>
		bool operator!= (const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs) const
	{
		return !(*this == GetImpl(rhs));
	}

	// Comparison with other types

	template <typename U>
	inline friend bool operator== (const SmartPtr& lhs, const U* rhs)
	{
		return lhs.mPointee == rhs;
	}

	template <typename U>
	inline friend bool operator== (const U* lhs, const SmartPtr& rhs)
	{
		return lhs == rhs.mPointee;
	}

	template <typename U>
	inline friend bool operator!= (const SmartPtr& lhs, const U* rhs)
	{
		return lhs.mPointee != rhs;
	}

	template <typename U>
	inline friend bool operator!= (const U* lhs, const SmartPtr& rhs)
	{
		return lhs != rhs.mPointee;
	}

	// Ordering functions: Resolve ambiguous calls (lhs vs rhs template operator)

	template
		<
		typename T1,
		template <class> class SP1,
		template <class> class OP1,
		class CP1,
		template <class> class CK1,
		typename U
		>
		bool operator<(const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs) const
	{
		return lhs < GetImpl(rhs);
	}

private:

	struct Insipid	// private struct used to disallow conversion
	{
		Insipid(PointerType) {}
	};

	typedef typename IF<CP::allow, PointerType, Insipid>::RET ConversionType;

public:

	operator ConversionType () const
	{
		return GetImpl(*this);
	}

};


// Ordering functions: Not defined by default: trigger error -> impl delegated to user's choice

template
<
	typename T1,
	template <class> class SP1,
	template <class> class OP1,
	class CP1,
	template <class> class CK1,
	typename U
>
bool operator<(const SmartPtr<T1, SP1, OP1, CP1, CK1>& lhs, const U& rhs);	// Not defined

template
<
	typename T1,
	template <class> class SP1,
	template <class> class OP1,
	class CP1,
	template <class> class CK1,
	typename U
>
bool operator<(const U& lhs, const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs);	// Not defined

template
<
	typename T1,
	template <class> class SP1,
	template <class> class OP1,
	class CP1,
	template <class> class CK1,
	typename U
>
bool operator<=(const SmartPtr<T1, SP1, OP1, CP1, CK1>& lhs, const U& rhs)
{
	return !(rhs < lhs);
}

template
<
	typename T1,
	template <class> class SP1,
	template <class> class OP1,
	class CP1,
	template <class> class CK1,
	typename U
>
bool operator<=(const U& lhs, const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs)
{
	return !(rhs < lhs);
}

template
<
	typename T1,
	template <class> class SP1,
	template <class> class OP1,
	class CP1,
	template <class> class CK1,
	typename U
>
bool operator>(const SmartPtr<T1, SP1, OP1, CP1, CK1>& lhs, const U& rhs)
{
	return rhs < lhs;
}

template
<
	typename T1,
	template <class> class SP1,
	template <class> class OP1,
	class CP1,
	template <class> class CK1,
	typename U
>
bool operator>(const U& lhs, const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs)
{
	return rhs < lhs;
}

template
<
	typename T1,
	template <class> class SP1,
	template <class> class OP1,
	class CP1,
	template <class> class CK1,
	typename U
>
bool operator>=(const SmartPtr<T1, SP1, OP1, CP1, CK1>& lhs, const U& rhs)
{
	return !(lhs < rhs);
}

template
<
	typename T1,
	template <class> class SP1,
	template <class> class OP1,
	class CP1,
	template <class> class CK1,
	typename U
>
bool operator>=(const U& lhs, const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs)
{
	return !(lhs < rhs);
}


// std::less function required by std algorithms

namespace std
{
	template
	<
		typename T1,
		template <class> class SP1,
		template <class> class OP1,
		class CP1,
		template <class> class CK1
	>
	struct less<SmartPtr<T1, SP1, OP1, CP1, CK1>> :
		public binary_function<SmartPtr<T1, SP1, OP1, CP1, CK1>, SmartPtr<T1, SP1, OP1, CP1, CK1>, bool>
	{
		bool operator() (const SmartPtr<T1, SP1, OP1, CP1, CK1>& lhs, const SmartPtr<T1, SP1, OP1, CP1, CK1>& rhs)
		{
			return std::less<T*>(GetImpl(lhs), GetImpl(rhs));
		}
	};
}